enum Distance {
    KM(  "kilometer", 1000.000 ),
     M(      "meter",    1.000 ),
    CM( "centimeter",    0.010 ),
    MM( "millimeter",    0.001 )

    Distance(name, scale) {
        this.name = name
        this.scale = scale
    }

    final String name
    private final BigDecimal scale

    def s(amt) { new Quantity( amount:amt, unit:this ) }
    def to(amt, to) { amt * this.scale / to.scale }

    def static km(Number n) { new Quantity( amount:n, unit:KM ) }
    def static  m(Number n) { new Quantity( amount:n, unit: M ) }
    def static cm(Number n) { new Quantity( amount:n, unit:CM ) }
    def static mm(Number n) { new Quantity( amount:n, unit:MM ) }
}

class Quantity {
    float amount
    def unit

    def to(otherUnit) {
        amount = unit.to( amount, otherUnit )
        unit = otherUnit
        this
    }

    def equals(other) {
        (unit != other.unit) ? equals( other.to( this.unit ) ) : amount == other.amount
    }

    String toString() { "$amount ${unit.name}s" }
}

println Distance.M.s(1).to( Distance.CM )

def values = Distance.values()
[values, values].combinations().sort { 1 / it[0].scale }.each {
    from = it[0]
    to = it[1]
    println "1 ${from.name}s = ${from.to( 1, to )} ${to.name}s"
}

use ( Distance ) {
    println 12.km()
    println 2500.cm().to( Distance.M )

    assert 12.m() == 1200.cm()
    Distance.M.s(1)

    assert (21.2).cm() == 212.mm()
}

assert Distance.km(20) == Distance.KM.s(20)